/*
Copyright 2011 Raymond Giorgi

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */

package edu.pitt.cs3530.predictors;

import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import edu.pitt.cs3530.configuration.FrontEndConfiguration;
import edu.pitt.cs3530.controllers.PredictorListener;
import edu.pitt.cs3530.misc.Mutexs;

public abstract class PredictorThread extends Thread {
	protected int counts[];
	protected String directory;
	protected long oldTime;
	protected long sleepTime;
	protected int noPoints;
	protected double interval;
	protected double currentLoad;
	protected double slope;
	private List<PredictorListener> listeners = null;
	protected double loads[];
	protected long lateDeadline;
	protected double qos;
	protected String qosName;

	public PredictorThread(long oldTime, long predictionTime, int noPoints,
			long sleepTime, String qosName, Map<String, String> parameterMap) {
		this.directory = FrontEndConfiguration.getInstance()
				.getTemporaryDirectory();
		this.oldTime = oldTime;
		this.sleepTime = sleepTime;
		this.noPoints = noPoints;
		this.qosName = qosName;
		System.out.println(oldTime + ":" + sleepTime + ":" + noPoints + ":" + qosName);
		counts = new int[noPoints];
		for (int i = 0; i < counts.length; i++) {
			counts[i] = 0;
		}
		interval = (double) oldTime / (double) noPoints;
		listeners = new LinkedList<PredictorListener>();
		loads = new double[(int) (oldTime / sleepTime)];
		
		this.lateDeadline = FrontEndConfiguration.getInstance()
				.getLateDeadline();
	}

	public void addListener(PredictorListener listener) {
		listeners.add(listener);
	}

	public void removeListener(PredictorListener listener) {
		listeners.remove(listener);
	}

	public void insertIntoCounts() {
		StringTokenizer tok = null;
		long currentTime = System.currentTimeMillis();
		for (int i = 0; i < counts.length; i++) {
			counts[i] = 0;
		}
		Mutexs.lockFileMutex();
		File files[] = (new File(directory)).listFiles();
		int filesSize = 0;
		Mutexs.unlockFileMutex();
		// System.out.println("no of files: " + files.length);
		double totalQos = 0.0;
		for (File file : files) {
			if (file.getName().startsWith("req-")
					|| file.getName().startsWith("fin-")) {
				if (file.getName().startsWith("fin")) {
					filesSize++;
				}
				tok = new StringTokenizer(file.getName(), "-");
				tok.nextToken();
				String sec = tok.nextToken();
				String uSec = tok.nextToken();
				if (!tok.hasMoreTokens()) {
					continue;
				}
				String endSec = tok.nextToken();
				String endUSec = tok.nextToken();
				long time = Long.parseLong(sec) * 1000 + Long.parseLong(uSec)
						/ 1000;
				long endTime = Long.parseLong(endSec) * 1000
						+ Long.parseLong(endUSec) / 1000;
				long dif = currentTime - time;
				totalQos += ((double) endTime - (double) time)
						/ this.lateDeadline;
				if (dif < oldTime) {
					int rounded = (int) ((dif) / interval + .5);
					// System.out.println("\t\t\trounded:" + rounded);
					if (rounded > 0.0) {
						if (rounded < counts.length - 1) {
							counts[rounded]++;
						} else {
							counts[counts.length - 1]++;
						}
					} else {
						counts[0]++;
					}
				}
			}
		}
		this.qos = totalQos / filesSize;
	}

	@Override
	public void run() {
		while (true) {
			try {
				insertIntoCounts();
				calculate();
				alertFrontEnd();
				sleep(sleepTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	protected void addLoad(double load) {
		for (int i = 1; i < loads.length; i++) {
			loads[i] = loads[i - 1];
		}
		loads[0] = load;
	}

	protected double[] differenceOfLoads() {
		double[] retval = new double[loads.length - 1];
		for (int i = 1; i < loads.length; i++) {
			retval[i - 1] = loads[i] - loads[i - 1];
		}
		return retval;
	}

	protected abstract void calculate();

	public abstract double predictedChange(long time);

	public abstract double currentLoad();

	public abstract double currentSlope();

	public synchronized double getAverageQos() {
		return this.qos;
	}

	protected void alertFrontEnd() {
		for (PredictorListener pl : listeners) {
			pl.alertChange(this);
		}
	}
}
